#ifndef _WebCrawler_H_
#define _WebCrawler_H_
#include <cstdio>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "url.hpp"
#include "Urlqueue.hpp"
#include "Socket.hpp"
#include "Configurator.hpp"
#include "Epoll.hpp"
#include "Plugin.hpp"
#include "Thread.hpp"
class UrlFilter;
class WebCrawler;

//网络爬虫
class WebCrawler {
public:
	int _curJobs; //当前抓取任务数
	time_t _start;//启动时间
	unsigned int _success;//成功次数
	unsigned int _failure;//失败次数
	pthread_mutex_t _mutex;//互斥锁
	pthread_cond_t _cond;//条件变量
	//Log _log;//日志
	Configurator _cfg;//配置器
	MultiIo _multiIo;//多路转接
	PluginMngr _pluginMngr;//插件管理
	UrlQueues _urlQueues;//统一资源定位符
	DnsThread _dnsThread;//域名解析
	SendThread _sendThread;//发送线程
public:
    //[in] 统一资源定位符过滤器
    WebCrawler(UrlFilter &filter)
        :_urlQueues(filter)// 初始化统一资源定位符队列
        ,_curJobs(0)// 初始化当前抓取任务数为0
        ,_start(time(NULL))// 初始化启动时间为当前系统时间
        ,_success(0)// 初始化成功次数为0
        ,_failure(0)//失败次数
    { 
        // 初始化失败次数为0
        // 初始化互斥锁
        pthread_mutex_init(&_mutex, NULL);
        // 初始化条件变量
        pthread_cond_init(&_cond, NULL);
    }

    /// @brief 析构器
	~WebCrawler ()
    {
        // 销毁条件变量
        pthread_cond_destroy (&_cond);
        // 销毁互斥锁
        pthread_mutex_destroy (&_mutex);
    }

	//初始化 //已守护进程运行
	void init (bool daemon = false)
    {
        // 通过配置器从指定的配置文件中加载配置信息
        _cfg.load("WebCrawler.cfg");
        // 若以精灵进程方式运行，// 使调用进程成为精灵进程
        if (daemon) initDaemon();
        // 通过插件管理器加载插件
        _pluginMngr.load();
        // 若切换工作目录至下载目录失败
        //if (chdir("../download") == -1)
            // 记录一般错误日志
            // m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
            //              "chdir: %s", strerror(errno));

        // 初始化最大文件描述符数为1K
        initMaxFiles(1024);
        // 将种子链接压入原始统一资源定位符队列
        initSeeds();
        // 启动域名解析线程
        initDns();
        // 启动状态定时器
        initTicker();
        // 启动发送线程
        initSend();
    }

    /// @brief 执行多路输入输出循环
	void exec (void)
    {
        // 无限循环
        while(true)
        {
            //输入输出事件结构数组
            epoll_event events[10];
            //等待所关注输入输出事件的发生，两秒超时
            int fds =_multiIo.wait(events, sizeof(events) / sizeof(events[0]), 2000);
            // 若超时或被信号中断
            if (fds <= 0)
            {
                // 若没有抓取任务且原始统一资源定位符
                // 队列和解析统一资源定位符队列都为空
                if (!_curJobs && _urlQueues.emptyRawUrl() && _urlQueues.emptyDnsUrl())
                {

                    // 等一秒
                    sleep(1);
                    // 若没有抓取任务且原始统一资源定位符 队列和解析统一资源定位符队列都为空
                    if (!_curJobs && _urlQueues.emptyRawUrl() && _urlQueues.emptyDnsUrl())
                        break; // 抓取任务完成，跳出循环
                } 
                // 否则
                else
                {  
                    // 若抓取任务到限且原始统一资源定位符
                    // 队列和解析统一资源定位符队列都为满
                    if (_curJobs == _cfg._maxJobs && _urlQueues.fullRawUrl() && _urlQueues.fullDnsUrl())
                    {
                        // 等一秒
                        sleep(1);
                        // 若抓取任务到限且原始统一资源定位符
                        // 队列和解析统一资源定位符队列都为满
                        if (_curJobs == _cfg._maxJobs && _urlQueues.fullRawUrl() && _urlQueues.fullDnsUrl())
                            // 清空原始统一资源定位符队列，避免死锁
                            _urlQueues.clearRawUrl();
                    }
                }
            }
            // 依次处理每个处于就绪状态的文件描述符
            for (int i = 0; i < fds; ++i)
            {
                // 从事件结构中取出套接字对象指针
                Socket *socket = (Socket *)events[i].data.ptr;
                // 若为异常事件
                if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP || !(events[i].events & EPOLLIN))
                {
                    // 记录警告日志
                    // m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
                    //              "套接字异常");
                    // 删除需要被关注的输入输出事件
                    _multiIo.del(socket->sockfd(), events[i]);
                    // 销毁套接字对象
                    delete socket;
                    // 继续下一轮循环
                    continue;
                }
                // 删除需要被关注的输入输出事件
                _multiIo.del(socket->sockfd(), events[i]);
                // 记录调试日志
                // m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
                //              "套接字%d上有数据可读，创建接收线程接收数据",
                //              socket->sockfd());
                // 创建接收线程对象并启动接收线程
                (new RecvThread(socket))->start();
            }
        }
        // 记录调试日志
        //m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__, "任务完成");
    }

    /// @brief 启动一个抓取任务
	void startJob ()
    {
        // 加锁互斥锁
        pthread_mutex_lock(&_mutex);
        // 若当前抓取任务数到限 等待条件变量
        while (_curJobs == _cfg._maxJobs)
            pthread_cond_wait(&_cond, &_mutex);
        // 从解析统一资源定位符队列弹出解析统一资源定位符
        DnsUrl dnsUrl = _urlQueues.popDnsUrl();
        // 用所弹出解析统一资源定位符创建套接字对象
        Socket *socket = new Socket(dnsUrl);
        // 若通过套接字对象发送超文本传输协议请求成功,当前抓取任务数加一
        if (socket->sendRequest()) ++_curJobs;
        // 否则,销毁套接字对象
        else delete socket;
        // 解锁互斥锁
        pthread_mutex_unlock(&_mutex);
    }

    /// @brief 停止一个抓取任务
    //是否成功
	void stopJob (bool success = true)
    {
        // 加锁互斥锁
        pthread_mutex_lock(&_mutex);
        // 当前抓取任务数减一，若其由到限变为未到限，唤醒等待条件变量的线程
        if (--_curJobs == _cfg._maxJobs - 1)
            pthread_cond_signal(&_cond);
        // 若成功抓取，成功次数加一
        if (success)
            ++_success;
        // 否则，失败次数加一
        else
            ++_failure;
        // 解锁互斥锁
        pthread_mutex_unlock(&_mutex);
    }

void WebCrawler::sigalrm (
	int signum // [输入] 信号编号
	) {
	// 抓取时间 = 当前时间 - 启动时间
	time_t t = time (NULL) - g_app->_start;

	// 记录信息日志
	// g_app->_log.printf (Log::LEVEL_INF, __FILE__, __LINE__,
	// 	"当前任务  原始队列  解析队列  抓取时间  成功次数  失败次数  成功率\n"
	// 	"------------------------------------------------------------------\n"
	// 	"%8d  %8u  %8u  %02d:%02d:%02d  %8u  %8u  %5u%%",
	// 	g_app->_curJobs,
	// 	g_app->_urlQueues.sizeRawUrl (),
	// 	g_app->_urlQueues.sizeDnsUrl (),
	// 	t / 3600, t % 3600 / 60, t % 60,
	// 	g_app->_success,
	// 	g_app->_failure,
	// 	g_app->_success ? g_app->_success * 100 /
	// 		(g_app->_success + g_app->_failure) : 0);
}

	

private:
	/// @brief SIGALRM(14)信号处理
	static void sigalrm (
		int signum ///< [in] 信号编号
	);

	/// @brief 使调用进程成为精灵进程
	void initDaemon (void) const
    {
        // 创建子进程
        pid_t pid = fork();

        // 若失败
        if (pid == -1)
            // 记录一般错误日志
            // _log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
            //              "fork: %s", strerror(errno));

        // 若为父进程
        if (pid)
            // 退出，使子进程成为孤儿进程并被init进程收养
            exit(EXIT_SUCCESS);

        // 子进程创建新会话并成为新会话中唯一进程组的组长
        // 进程，进而与原会话、原进程组和控制终端脱离关系
        setsid();

        // 打开空设备文件
        int fd = open("/dev/null", O_RDWR, 0);
        // 若成功
        if (fd != -1)
        {
            // 复制空设备文件描述符到标准输入
            dup2(fd, STDIN_FILENO);
            // 复制空设备文件描述符到标准输出
            dup2(fd, STDOUT_FILENO);
            // 复制空设备文件描述符到标准出错
            dup2(fd, STDERR_FILENO);
            // 若空设备文件描述符大于标准出错
            if (fd > STDERR_FILENO)
                // 关闭空设备文件描述符
                close(fd);
        }

        // 若配置器中的日志文件路径非空且打开(创建)日志文件成功
        if (!_cfg._logFile.empty() && (fd = open(_cfg._logFile.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0664)) != -1)
        {
            // 复制日志文件描述符到标准输出
            dup2(fd, STDOUT_FILENO);
            // 若日志文件描述符大于标准出错
            if (fd > STDERR_FILENO)
                // 关闭日志文件描述符
                close(fd);
        }
    }


	/// @brief 初始化最大文件描述符数
	/// @retval true  成功
	/// @retval false 失败
    ///< [in] 最大文件描述符数
	bool initMaxFiles (rlim_t maxFiles ) const
    {
        // 资源限制结构
        rlimit rl;

        // 若获取当前进程可同时打开的最大文件数失败
        if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
        {
            // 记录警告日志
            // m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
            //              "getrlimit: %s", strerror(errno));
            // // 返回失败
            return false;
        }

        // 若最大文件描述符数超过系统允许的极限
        if (maxFiles > rl.rlim_max)
        {
            // 记录警告日志
            // m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
            //              "文件描述符上限不能超过%lu", rl.rlim_max);
            // // 返回失败
             return false;
        }

        // 若设置当前进程可同时打开的最大文件数失败
        rl.rlim_cur = maxFiles;
        if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
        {
            // // 记录警告日志
            // m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
            //              "setrlimit: %s", strerror(errno));
            // // 返回失败
            return false;
        }

        // 返回成功
        return true;
}

	/// @brief 将种子链接压入原始统一资源定位符队列
	void initSeeds (void)
    {
        // 若配置器中的种子链接为空
        if (_cfg._seeds.empty())
        {    // 记录一般错误日志
            // m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
            //              "没种子咋干活嘞？");
        }
        // 拆分种子链接字符串，以逗号为分隔符，不限拆分次数
        std::vector<std::string> seeds = StrKit::split(_cfg._seeds, ",", 0);

        // 依次处理每个种子链接
        for (std::vector<std::string>::iterator it = seeds.begin(); it != seeds.end(); ++it)
        {
            // 若规格化成功压入原始统一资源定位符队列
            if (RawUrl::normalized(*it))
                _urlQueues.pushRawUrl(RawUrl(*it));
        }
    }

	/// @brief 启动域名解析线程
	void initDns (void)
    {
        // 通过域名解析线程对象启动域名解析线程
        _dnsThread.start();
        /*
        // 若解析统一资源定位符队列为空，延时再查，最多查八次
        for (int try = 1; m_urlQueues.emptyDnsUrl () && try < 8;
            usleep (10000 << try++));

        // 若解析统一资源定位符队列为空
        if (m_urlQueues.emptyDnsUrl ())
            // 记录一般错误日志
            m_log.printf (Log::LEVEL_ERR, __FILE__, __LINE__,
                "解析URL队列空");
        */
    }

	/// @brief 启动状态定时器
	void initTicker (void) const
    {
        // 若配置器中的状态间隔有效
        if (_cfg._statInterval > 0)
        {
            // 若设置SIGALRM(14)信号处理失败
            if (signal(SIGALRM, sigalrm) == SIG_ERR)
            {  /*
                struct sigaction act = {};
                act.sa_handler = sigalrm;
                act.sa_flags = SA_RESTART;
                if (sigaction (SIGALRM, &act, NULL) == -1)
                */
                // 记录一般错误日志
                // m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
                //              "signal: %s", strerror(errno));
                lg(Error, "signal error, %s: %d", strerror(errno), errno);
            }
            // 间隔时间
            itimerval it;
            // 初始间隔(秒)
            it.it_value.tv_sec = _cfg._statInterval;
            // 初始间隔(微秒)
            it.it_value.tv_usec = 0;
            // 重复间隔(秒)
            it.it_interval.tv_sec = _cfg._statInterval;
            // 重复间隔(微秒)
            it.it_interval.tv_usec = 0;

            // 若设置真实间隔定时器失败
            if (setitimer(ITIMER_REAL, &it, NULL) == -1)
            {   // 记录一般错误日志
                // _log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
            }  //              "setitimer: %s", strerror(errno));
        }
    }
	/// @brief 启动发送线程
	void initSend (void)
    {
        // 通过发送线程对象启动发送线程
	    _sendThread.start ();
    }
};

extern WebCrawler* g_app; 

#endif //_WebCrawler_H_


